#

| Chapter 9 Saving Time on the Command Line |

## 9.11 Repeating a Command with a foreach Loop

When some people need to repeat a command on several files, the first thing they think of is C shell history (11.5):

    -v
    % cat -t -v /usr/fran/report | pg
       ...
    % ^fran/report^rob/file3
    cat -t -v /usr/rob/file3 | pg
       ...
    % ^3^21
    cat -t -v /usr/rob/file21 | pg
       ...
    %

That kind of thing can be easier with the C shell's foreach loop. (In the Bourne and Korn shells, use a for (9.12) loop.) You give the loop a list of the words that will change each time the command line is run. In this example, it's a list of filenames. The loop will step through the words, one by one, storing a word into a shell variable (6.8), then running the command(s). The loop goes on until it has read all the words. For example:

>     % foreach file (/usr/fran/report /usr/rob/file3 /usr/rob/file21)
>     ? cat -t -v $file | pg
>     ? end
>        ...Shell runs cat -t -v /usr/fran/report | pg...
>        ...Shell runs cat -t -v /usr/rob/file3 | pg...
>        ...Shell runs cat -t -v /usr/rob/file21 | pg...
>     %

The question marks (`?`) are secondary prompts (9.13); the C shell will keep printing them until you type the command end. Then the loop runs.

The list between the parentheses doesn't have to be filenames. Among other things, you can use wildcards (1.16), backquotes (9.16) (command substitution), variables (6.8, 6.1), and the C shell's handy curly brace (`{}`) operators (9.5). For example, you could have typed the above loop this way:

>     % foreach file (/usr/fran/report /usr/rob/file{3,21})
>     ? cat -t -v $file | pg
>     ? end

If you want the loop to stop before or after running each command, add the C shell operator `$<`. It reads keyboard input and waits for a RETURN. In this case, you can probably ignore the input; you'll use `$<` to make the loop wait. For example, to make the loop above prompt before each command line:

    set
    % foreach file (/usr/fran/report /usr/rob/file{3,21})
    ? echo -n "Press RETURN to see $file-"
    ? set x="$<"
    ? cat -t -v $file | pg
    ? end
    Press RETURN to see /usr/fran/report- [RETURN]
       Shell runs cat -t -v /usr/fran/report | pg...
    Press RETURN to see /usr/rob/file3- [RETURN]
       Shell runs cat -t -v /usr/rob/file3 | pg...
    Press RETURN to see /usr/rob/file21- [RETURN]
       Shell runs cat -t -v /usr/rob/file21 | pg...

The loop parameters don't need to be filenames. For instance, you could send a personalized mail (1.33) message to five people this way: [1]

> [1] If you're sending lots of mail messages with a loop, your system mailer may get overloaded. In that case, it's a good idea to put a command like `sleep` `5` (40.2) on a separate line before the `end`. That will give the mailer five seconds to send each message.

    cat -
    % foreach person (John Cathy Agnes Brett Elma)
    ? echo "Dear $person," | cat - formletter | mail $person
    ? end

The first line of the first letter will be "Dear John,"; the second letter "Dear Cathy,"; and so on.

Want to take this idea further? It's a part of shell programming (44.1). I usually don't recommend (47.2) shell programming with the C shell, but this is a handy technique to use interactively.

- JP

9.10 Filename Completion Isn't Always the Answer |  | 9.12 The Bourne Shell for Loop